///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/fqmc/energy.cpp
///
/// \brief   implementation file for the monte carlo energy function
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#include<complex>
#include<vector>
#include<string>
#include<numeric>
#include<cassert>
#include<algorithm>

#include<boost/format.hpp>
#include<boost/shared_ptr.hpp>

#include<formic/fqmc/energy.h>
#include<formic/fqmc/energy_acc.h>
#include<formic/fqmc/sampler.h>
#include<formic/fqmc/accumulator.h>
#include<formic/wfn/configuration.h>
#include<formic/fqmc/vmc_walker.h>
#include<formic/wfn/wfn.h>
#include<formic/input/base.h>
#include<formic/mpi/interface.h>
#include<formic/numeric/numeric.h>
#include<formic/output/output.h>
#include<formic/exception.h>
#include<formic/lapack/interface.h>
#include<formic/random/random.h>

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy using the supplied guiding and value functions
///
/// \param[in]      userinp        the user's input options
/// \param[in]      sampler        object controlling the sampling
/// \param[in]      vf             a shared pointer to the value function
///
/// \return  the energy
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> double formic::fqmc::energy(const formic::InputBase & userinp,
                                              boost::shared_ptr<formic::fqmc::Sampler<S> > & sampler,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & vf,
                                              double & sdev,
                                              double & serr,
                                              const bool print) {

  // create an accumulator for the energy
  boost::shared_ptr<formic::Accumulator<S> > eacc( new formic::fqmc::EnAcc<S>(sampler->gf(),
                                                                              vf,
                                                                              userinp.get<int>("energy_ac_length"),
                                                                              userinp.get<bool>("exact_sampling"),
                                                                              userinp.get<bool>("print_config_info")) );

  // sample the energy
  sampler->simple_sample() << vf << eacc;

  // get a derived class pointer to the energy accumulator
  formic::fqmc::EnAcc<S> * eacc_ptr = dynamic_cast<formic::fqmc::EnAcc<S> *>(eacc.get());

  // print statistics of energy sampling
  if (print)
    eacc_ptr->print_statistics(formic::of);

  // get the standard deviations
  sdev = std::abs( std::sqrt(eacc_ptr->variance()) );

  // get the statistical error
  serr = eacc_ptr->serr();

  // return the energy
  return eacc_ptr->energy();

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy using the supplied guiding and value functions
///
/// \param[in]      userinp        the user's input options
/// \param[in]      burn_len       the burn-in length
/// \param[in]      samp_len       the sample length
/// \param[in]      init_config    the initial configuration
/// \param[in]      rng            the random number generator to use
/// \param[in]      gf             a shared pointer to the guiding function
/// \param[in]      vf             a shared pointer to the value function
///
/// \return  the energy
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> double formic::fqmc::energy(const formic::InputBase & userinp,
                                              const int burn_len,
                                              const int samp_len,
                                              const formic::Configuration & init_config,
                                              const formic::LaggedFibonacci<> & rng,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & gf,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & vf,
                                              double & sdev,
                                              double & serr,
                                              const bool print) {

  // create sampler
  boost::shared_ptr<formic::fqmc::Sampler<S> > sampler( new formic::fqmc::Sampler<S>(userinp, burn_len, samp_len, init_config, rng, gf) );

  // compute and return the energy
  return formic::fqmc::energy(userinp, sampler, vf, sdev, serr, print);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy of the supplied coefficient object
///
/// \param[in]      userinp        the user's input options
/// \param[in]      co             a shared pointer to the coefficient object
///
/// \return  the energy
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> double formic::fqmc::energy(const formic::InputBase & userinp, const boost::shared_ptr<formic::CoeffObj<S> > & co, double & sdev, double & serr, const bool print) {

  // get initial configuration
  formic::Configuration init_config(userinp);

  // get burn in and sample lengths
  const int burn_len = userinp.get<int>("energy_burn_len");
  const int samp_len = userinp.get<int>("energy_samp_len");

  // compute and return energy
  return formic::fqmc::energy(userinp, burn_len, samp_len, init_config, formic::LaggedFibonacci<>(), co, co, sdev, serr, print);

}

template double formic::fqmc::energy(const formic::InputBase & userinp, const boost::shared_ptr<formic::CoeffObj<double> > & co, double &, double &, const bool);
template double formic::fqmc::energy(const formic::InputBase & userinp, const boost::shared_ptr<formic::CoeffObj<std::complex<double> > > & co, double &, double &, const bool);
